/***************************************************************************
 *   Copyright (C) 2009 by Geoffrey McRae   *
 *   geoff@spacevs.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>

#include "mediaplayer.h"
#include "common/player.h"

/* Destructs a packet from the lPackets linked list */
inline void mp_av_free_packet(void *_data) {
	AVPacket *p = _data;
	av_free_packet(p);
	free(p);
}

/* Returns if the player is running */
inline bool mp_isrunning(mp_t *p) {
	pthread_rwlock_rdlock(&p->mRunning);
	if (!p->bRunning) {
		pthread_rwlock_unlock(&p->mRunning);
		return false;
	}
	pthread_rwlock_unlock(&p->mRunning);
	return true;
}

/* Tells the clock that the supplied stream has been primed and then blocks until playback is started */
inline void mp_stream_primed(mp_stream_t *s) {
	if (!s->bPrimed) {
		pthread_mutex_lock(&s->p->mClock);
		s->bPrimed = true;
		pthread_cond_broadcast(&s->p->cClock);
		INF("Signal stream %d primed", s->iIndex);
		
		while(mp_isrunning(s->p) && (!s->p->bPlaying)) {
			pthread_cond_wait(&s->p->cClock, &s->p->mClock);
		}
		INF("Stream %d started", s->iIndex);
		pthread_mutex_unlock(&s->p->mClock);
	}
}

void mp_prime_streams(mp_t *p, mp_stream_t **sync) {
	int i;
	bool bRun;
	mp_stream_t *s;

	*sync = NULL;
	bRun = false;

	INF("%s", "Waiting for streams to be primed");
	/* Wait for all the streams to be primed before starting the clock */
	for(i = 0; i < array_count(p->aStreams); ++i) {
		s = array_get(p->aStreams, i);
		/* Choose the sync source */
		if ((!*sync) && (s->pStream->codec->codec_type == CODEC_TYPE_AUDIO)) {
			INF("Syncing clock to audio stream %d", s->iIndex);
			s->bSyncSource = true;
			*sync = s;
		} else	s->bSyncSource = false;

		INF("Waiting for stream %d to become ready", s->iIndex);
		while((bRun = mp_isrunning(p)) && (!s->bPrimed)) {
			pthread_cond_wait(&p->cClock, &p->mClock);
		}
		if (bRun) INF("Stream %d is primed", s->iIndex);
	}
	if (bRun) INF("%s", "All streams primed and ready to go");
}

/* This is the master clock thread */
void *mp_thread_clock(void *_data) {
	mp_t 	*p = _data;
	mp_stream_t	*sync;

	struct timespec	*ts, *last, *tmp;
	double		diff, clock;

	ts	= malloc(sizeof(struct timespec));
	last	= malloc(sizeof(struct timespec));

	pthread_mutex_lock(&p->mClock);
	/* Set the clock */
	p->dClock = 0;
	sync = NULL;

	/* Prime the streams */
	mp_prime_streams(p, &sync);

	/* Tell all the streams to start */
	p->bPlaying = true;
	pthread_cond_broadcast(&p->cClock);
	pthread_mutex_unlock(&p->mClock);

	/* Run the clock */
	clock_gettime(CLOCK_REALTIME, last);
	while(mp_isrunning(p)) {
		pthread_rwlock_rdlock(&p->lClock);
		if (p->bPaused) {
			pthread_rwlock_unlock(&p->lClock);
			usleep(100);
			clock_gettime(CLOCK_REALTIME, last);
			continue;
		}
		pthread_rwlock_unlock(&p->lClock);

		/* If we have a sync source, then wait for a condition signal from it */
		if (sync) {
			clock_gettime(CLOCK_REALTIME, ts);
			mp_add_time(ts, 100000);
			pthread_mutex_lock(&p->mClock);
			wait:
			if(pthread_cond_timedwait(&p->cClock, &p->mClock, ts) == 0) {
				if (!sync->bClockUpdated) goto wait;
				sync->bClockUpdated = false;

				clock_gettime(CLOCK_REALTIME, last);
				pthread_rwlock_wrlock(&p->lClock);
				p->dClock	= sync->dClock;
				clock		= p->dClock;
				pthread_rwlock_unlock(&p->lClock);
				pthread_mutex_unlock(&p->mClock);

				/* run all audio sync events */
				ll_foreach(p->lEventsAS, false, mp_foreach_event_all, &clock);
				goto run;
			}
			pthread_mutex_unlock(&p->mClock);
		} else {
			/* No sync source, so just sleep a while */
			usleep(100);
		}

		pthread_rwlock_wrlock(&p->lClock);
		if (p->bForceClock) {
			pthread_mutex_lock(&p->mClock);

			/* Prime the streams and then tell the demuxer we are done */
			mp_prime_streams(p, &sync);
			p->bForceClock = false;
			pthread_cond_broadcast(&p->cClock);

			/* Resume playback */
			p->bPlaying = true;
			pthread_cond_broadcast(&p->cClock);
			pthread_mutex_unlock(&p->mClock);

			/* Get the current time for the next loop */
			clock_gettime(CLOCK_REALTIME, last);
		} else {
			/* Get the time and calculate the new clock */
			clock_gettime(CLOCK_REALTIME, ts);
			diff = ((double)(ts->tv_nsec - last->tv_nsec) / 1000000000L) + (double)(ts->tv_sec - last->tv_sec);
			p->dClock += diff;
			clock = p->dClock;

			/* Swap the pointers for the next run */
			tmp  = ts;
			ts   = last;
			last = tmp;
		}
		pthread_rwlock_unlock(&p->lClock);

		run:
		/* Run any pts or tick events */
		ll_foreach(p->lEventsPTS, false, mp_foreach_event_pts, &clock);
		ll_foreach(p->lEventsMC , false, mp_foreach_event_all, &clock);
	}

	free(ts);
	free(last);

	/* Run all events so they get dropped by their callback functions */
	ll_foreach(p->lEventsPTS, false, mp_foreach_event_all, NULL);

	pthread_exit(NULL);
}

/* This is the stream thread */
void* mp_thread_stream(void *_data) {
	mp_stream_t *s = _data;

	switch(s->pStream->codec->codec_type) {
		case CODEC_TYPE_AUDIO: mp_stream_audio(s); break;
		case CODEC_TYPE_VIDEO: mp_stream_video(s); break;

		default:
			ERR("Unable to handle stream type %d", s->pStream->codec->codec_type);
			mp_stream_primed(s);
	}

	pthread_rwlock_wrlock(&s->lStop);
	s->bStop = true;
	pthread_rwlock_unlock(&s->lStop);

	avcodec_flush_buffers(s->pStream->codec);
	pthread_exit(NULL);
}

/* This is the main demuxer thread */
void* mp_thread_demuxer(void *_data) {
	mp_t	*p	= _data;
	AVPacket	pkt, *pSave;
	int		i;
	mp_stream_t	*s;
	bool		bFinished;
	bool		bAllStopped;

	/* We are not playing yet */
	p->bPlaying	= false;
	bFinished	= false;
	for(i = 0; i < array_count(p->aStreams); ++i) {
		s = array_get(p->aStreams, i);
		s->bPrimed	= false;
		s->bStop	= false;
		s->bRunning	= true;
	}

	/* Start the master clock thread */
	pthread_create(&p->tClock, NULL, mp_thread_clock, p);

	/* Start each stream thread up */
	for(i = 0; i < array_count(p->aStreams); ++i) {
		s = array_get(p->aStreams, i);
		pthread_create(&s->tThread, NULL, mp_thread_stream, s);
	}

	while(mp_isrunning(p)) {
		if (!bFinished) {
			/* read a frame from the context */
			if (av_read_frame(p->pFormatCtx, &pkt) < 0) {
				if (url_feof(p->pFormatCtx->pb)) {
					INF("%s", "Finished demuxing");
					bFinished = true;

					/* Tell all the packet queues to drain as there is no more data */
					for(i = 0; i < array_count(p->aStreams); ++i) {
						s = array_get(p->aStreams, i);
						ll_drain(s->lPackets);
					}
				} else	ERR("%s", "Error reading frame");
			}
		
			/* queue the packets */
			for(i = 0; i < array_count(p->aStreams); ++i) {
				s = array_get(p->aStreams, i);
				if (s->iIndex == pkt.stream_index) {
					av_dup_packet(&pkt);
					pSave	= av_malloc(sizeof(AVPacket));
					*pSave	= pkt;				
					ll_push(s->lPackets, pSave);
					break;
				}
			}
		} else {
			/* Wait for the streams to finish */
			bAllStopped = true;
			for(i = 0; i < array_count(p->aStreams); ++i) {
				s = array_get(p->aStreams, i);
				pthread_rwlock_rdlock(&s->lStop);
				if (!s->bStop) bAllStopped = false;
				pthread_rwlock_unlock(&s->lStop);
				if (!bAllStopped) break;
			}
			if (bAllStopped) break;
			usleep(1000);
		}

		/* Watch for a seek */
		pthread_rwlock_rdlock(&p->lSeek);
		if (p->bSeek) {
			INF("Seek to %f", p->dSeek);
			pthread_rwlock_unlock(&p->lSeek);

			if (av_seek_frame(p->pFormatCtx, -1, p->dSeek * AV_TIME_BASE, 0) < 0) {
				ERR("%s", "Failed to seek");
			} else {
				/* Force the new clock value */
				pthread_mutex_lock(&p->mClock);
				p->bPlaying = false;

				/* Clear all the events and packets */
				ll_foreach(p->lEventsPTS, false, mp_foreach_event_all, NULL);
				for(i = 0; i < array_count(p->aStreams); ++i) {
					s = array_get(p->aStreams, i);
					ll_plug(s->lPackets);
					ll_empty(s->lPackets);
					s->bPrimed = false;
					avcodec_flush_buffers(s->pStream->codec);
				}

				/* Force the new clock  */
				p->bForceClock	= true;
				p->dClock	= p->dSeek;

				pthread_mutex_unlock(&p->mClock);
				bFinished = false;
			}

			pthread_rwlock_wrlock(&p->lSeek);
			p->bSeek = false;
		}
		pthread_rwlock_unlock(&p->lSeek);
	}
	INF("%s", "Demuxer finished");

	pthread_rwlock_wrlock(&p->mRunning);
	p->bRunning = false;
	pthread_rwlock_unlock(&p->mRunning);

	/* Empty all the packet queues/semaphores and wait for the threads to terminate */
	for(i = 0; i < array_count(p->aStreams); ++i) {
		s = array_get(p->aStreams, i);
		pthread_rwlock_wrlock(&s->lRunning);
		s->bRunning = false;
		pthread_rwlock_unlock(&s->lRunning);
	
		ll_empty(s->lPackets);
		INF("Joining stream %d", i);
		pthread_join(s->tThread, NULL);
	}

	/* Wait for the clock to finish */
	pthread_cond_signal(&p->cClock);
	pthread_join(p->tClock, NULL);

	/* seek the stream back to the start */
	av_seek_frame(p->pFormatCtx, -1, 0, 0);

	pthread_exit(NULL);
}
